# coding: utf-8

from __future__ import absolute_import, division, print_function, unicode_literals

import json
import os
import os.path
import re

import httpx

from apitest.engine.client import Client
from apitest.engine.config import att_config, main_config
from apitest.engine.const.enums import HTTPEventHooksEnum, Method
from apitest.engine.const.model import Auth, EnvModel
from apitest.engine.env import DEFAULT_IDC, run_env
from apitest.engine.logging import logger


class HttpxClient(Client):

    def __init__(self, add_env=True, logging=True, timeout=10, **kwargs):
        """
        :param add_env: 是否需要添加环境标签
        :param logging: 是否需要打印日志
        :param timeout: 超时时间
        :param kwargs:
        """
        super(HttpxClient, self).__init__()

        hooks = {
            HTTPEventHooksEnum.REQUEST.value: [self.route_hook],
            HTTPEventHooksEnum.RESPONSE.value: []
        }
        if logging:
            hooks = {
                HTTPEventHooksEnum.REQUEST.value: [self.route_hook, self.request_logging_hook],
                HTTPEventHooksEnum.RESPONSE.value: [self.response_logging_hook]
            }
        self.client = httpx.Client(event_hooks=hooks, timeout=timeout, verify=False, **kwargs)
        self.update_headers("Content-Type", "application/json")
        if add_env:
            if run_env.is_boe():
                self.update_headers("x-use-boe", "1")
            elif run_env.is_ppe():
                self.update_headers("x-use-ppe", "1")
            if run_env.env_tag and not (run_env.is_online() and not run_env.is_canary()):
                self.update_headers("x-tt-env", run_env.env_tag)
        # 采样
        if logger.level == "DEBUG":
            self.update_headers("get-svc", "1")
        # Tesla来源注入
        if os.getenv("tesla_task_url"):
            self.update_headers("referer", os.getenv("tesla_task_url"))

    def send_request(self, path, method: Method = None, *args, **kwargs):
        if method is None or method.value not in list(Method.__members__):
            raise RuntimeError("method must exist, and the type is Method")
        method = method.value.upper()
        return self.client.request(method, path, *args, **kwargs)

    @staticmethod
    def logging_hook(response: httpx.Response):
        """
        输出美化
        :param response:
        :return:
        """
        method = response.request.method
        path = response.request.url.path
        request_header = "\n\t".join(f"{key}: {value}" for key, value in response.request.headers.items())
        request_body = response.request.content.decode('utf-8')
        response_header = "\n\t".join(f"{key}: {value}" for key, value in response.headers.items())
        response_body = response.read().decode('utf-8')
        log_str = "[HTTP] {} {}\n  Request:\n\tCONNECT: {} {}\n\t{}\n\n\t{}\n\n  Response:\n\t{} {} {}\n\t{}\n\n\t{}" \
                  "".format(method, path, response.url, response.http_version, request_header, request_body,
                            response.http_version, response.status_code, response.reason_phrase, response_header,
                            response_body
                            )
        logger.debug("request detail is:\n%s", log_str)
        log_id = response.headers.get("X-Tt-LogId", None)
        logger.info(f"request url is: {response.request.url}, log id is: {log_id}. runtime env is: {run_env.region}, "
                  f"env label is: {run_env.env_tag}"
                  )

    @staticmethod
    def request_logging_hook(request: httpx.Request):
        body = {}
        if request.content:
            body = json.loads(request.content)
        logger.info("[Httpx Request] method: %s, url: %s, headers: %s, body: %s" % (
            request.method, str(request.url), dict(request.headers), body)
                  )

    @staticmethod
    def response_logging_hook(response: httpx.Response):
        body = {}
        if response.read():
            try:
                body = json.loads(response.read())
            except json.decoder.JSONDecodeError:
                body = response.read()
        logger.info("[Httpx Response] status: %s %s, headers: %s, body: %s" % (
            response.status_code, response.reason_phrase, dict(response.headers), body)
                  )

    @staticmethod
    def assert_status_hook(response):
        response.raise_for_status()

    @staticmethod
    def route_hook(request: httpx.Request):
        params = dict(request.url.params)
        path = re.sub(r"[:{](\w+)}?", lambda m: params.pop(m.group(1)), request.url.path)
        request.url = httpx.URL(request.url, path=path, params=params)

    def update_headers(self, key: str, value=None):
        if value is None:
            self.client.headers.pop(key, None)
        else:
            self.client.headers.update({key: value})


class HttpClient:

    def __init__(self, app: str, psm, auth: Auth = Auth(), env: EnvModel = None, **kwargs):
        self.app = app
        self.conf = self.__get_config()
        self.psm = psm
        # self.psm_str = self.psm.replace(".", "_")
        self.env = run_env
        if env:
            self.env.set_env(env)
            self.env.init_env()
        if not self.conf.apply_psm and self.psm not in self.conf.apply_psm:
            raise Exception("psm: %s has no permissions" % self.psm)
        self.host = self.conf.host.get(self.env.idc) or self.conf.host.get(DEFAULT_IDC.get(self.env.region))
        if self.host is None:
            raise Exception("idc: %s host 没有配置。config: %s" % (self.env.idc, self.conf.dict()))

        self.auth = auth or Auth()
        self.__build_auth()
        self.client = HttpxClient(base_url=self.host, **kwargs).client
        self.cookie_init()

    def __get_config(self):
        # 配置优先从用户项目取，没有则从apitest里取
        app_conf = main_config.app.get(self.app)
        if app_conf is None:
            app_conf = att_config.app.get(self.app)
            if app_conf is None:
                raise Exception("app: %s not found in %s" % (self.app, cf.main_file))
        return app_conf

    def __build_auth(self):
        if self.conf.auth is None or (self.conf.auth.header is None and self.conf.auth.cookie is None):
            logger.warning("not found auth, please check psm config.")
            return
        if self.conf.auth.header:
            for k, v in self.conf.auth.header.items():
                if not self.auth.oauth.get(k) and v is not None:
                    self.auth.oauth.update({k: v.get(self.env.region)})

        if self.conf.auth.cookie:
            for k, v in self.conf.auth.cookie.items():
                if not self.auth.cookie.get(k) and v is not None:
                    self.auth.cookie.update({k: v.get(self.env.region)})

    def cookie_init(self):
        if self.auth.oauth:
            self.client.headers.update(self.auth.oauth)
        if self.auth.cookie:
            for k, v in self.auth.cookie.items():
                self.client.cookies.set(k, v, domain=self.host[self.host.find(".") + 1:])

    def reset_host(self, host=None):
        self.host = host
        self.client.base_url = self.host
